
import acm.graphics.*;
import acm.program.*;
import acm.util.*;
 
import java.applet.*;
import java.awt.*;
import java.awt.event.*;
 
public class test extends GraphicsProgram implements MouseListener {
 
/** Width and height of application window in pixels */
	public static final int APPLICATION_WIDTH = 400;
	public static final int APPLICATION_HEIGHT = 600;
 
/** Dimensions of game board (usually the same) */
	private static final int WIDTH = APPLICATION_WIDTH;
	private static final int HEIGHT = APPLICATION_HEIGHT;
 
/** Dimensions of the paddle */
	private static final int PADDLE_WIDTH = 60;
	private static final int PADDLE_HEIGHT = 10;
 
/** Offset of the paddle up from the bottom */
	private static final int PADDLE_Y_OFFSET = 30;
 
/** Number of bricks per row */
	private static final int NBRICKS_PER_ROW = 10;
 
/** Number of rows of bricks */
	private static final int NBRICK_ROWS = 10;
 
/** Separation between bricks */
	private static final int BRICK_SEP = 4;
 
/** Width of a brick */
	private static final int BRICK_WIDTH =
	  (WIDTH - (NBRICKS_PER_ROW - 1) * BRICK_SEP) / NBRICKS_PER_ROW;
 
/** Height of a brick */
	private static final int BRICK_HEIGHT = 8;
 
/** Radius of the ball in pixels */
	private static final int BALL_RADIUS = 10;
 
/** Offset of the top brick row from the top */
	private static final int BRICK_Y_OFFSET = 70;
 
/** Number of turns */
	private static final int NTURNS = 3;
 
	/** the starting pause delay between paddle hits **/ 
	private static final int PAUSE_DELAY = 20;
	/** text size of the display text */
	private static final int TEXT_SIZE = 5;
	/** ball speed */
	private static final double BALL_SPEED = 3.0;
	/** extras - if you catch the extra score, this is the extra value */
	private static final int EXTRA_SCORE_VALUE = 100;
	/** extras speed coming down the screen */
	private static final double EXTRAS_SPEED = 3.0;
 
	public void mouseMoved(MouseEvent e)
	{
		Point movePos = e.getPoint();
		if (movePos.x < (WIDTH- PADDLE_WIDTH))
			paddleObject.setLocation(movePos.x,HEIGHT - (PADDLE_Y_OFFSET + PADDLE_HEIGHT));
	}
 
	// setup the bricks, with different colours
	private void CreateBricks()
	{
// if there is any more colours, rows or less then this could be a problem of creating the list of colours.
		colours[0] = Color.RED;
		colours[1] = Color.RED;
		colours[2] = Color.ORANGE;
		colours[3] = Color.ORANGE;
		colours[4] = Color.YELLOW;
		colours[5] = Color.YELLOW;
		colours[6] = Color.GREEN;
		colours[7] = Color.GREEN;
		colours[8] = Color.CYAN;
		colours[9] = Color.CYAN;
 
		int startX, startY = BRICK_Y_OFFSET;
		GRect bricks;
		// iRow = going downwards
		for (int iRow =0; iRow < NBRICK_ROWS; iRow++)
		{
			startX = BRICK_SEP;
			// iAisle = going across
			for (int iAisle = 0; iAisle < NBRICKS_PER_ROW; iAisle++)
			{
				bricks = new GRect(startX,startY, BRICK_WIDTH,BRICK_HEIGHT);
				bricks.setFillColor(colours[iRow]);
				bricks.setFilled(true);
				bricks.setColor(colours[iRow]);
				add(bricks);
				startX += BRICK_WIDTH + BRICK_SEP;
			}
			startY += BRICK_HEIGHT + BRICK_SEP;
		}
	}
 
	private void addPaddle()
	{
		paddleObject = new GRect((WIDTH/2) - (PADDLE_WIDTH/2), HEIGHT - (PADDLE_Y_OFFSET + PADDLE_HEIGHT),
								PADDLE_WIDTH, PADDLE_HEIGHT);
		paddleObject.setColor(Color.BLACK);
		paddleObject.setFillColor(Color.BLACK);
		paddleObject.setFilled(true);
		add(paddleObject);
	}
 
	private void setUpBall()
	{
		// middle !!
		ballX = WIDTH / 2;
		ballY = HEIGHT / 2;
		// ballVX / VY are the velocity of movement 
		ballObject = new GOval(ballX, ballY, BALL_RADIUS, BALL_RADIUS);
		ballObject.setFillColor(Color.BLACK);
		ballObject.setFilled(true);
		add(ballObject);
		// setup the ball to move a random direction
		ballVX = rgen.nextDouble(1.0, 3.0);
		if (rgen.nextBoolean(0.5)) ballVX -= ballVX;
		ballVY = BALL_SPEED;
	}
 
	// move the ball and check for collision around the screen, if gone to the bottom return true else false
	private Boolean moveBall()
	{
		GPoint ballPoint = ballObject.getLocation();
		if (ballPoint.getX() > WIDTH) ballVX = -ballVX;
		if (ballPoint.getX() <= 0) ballVX = -ballVX;
		if (ballPoint.getY() <= 0) ballVY = -ballVY;
		if (ballPoint.getY() > HEIGHT) return true;//ballVY = -ballVY; // basically lost
		ballObject.move(ballVX,ballVY);
		return false;
	}
 
	// get any Graphics Object surrounding the ball Object, be it the bat and ball, or bat and extras
	private GObject getCollidingObject(GObject ballObj)
	{
		if (!ballObj.isVisible()) return null;
		GPoint ballPoint = ballObj.getLocation();
		GObject coll;
		Point addingPoints[] = new Point[4];
		addingPoints[0]= new Point(0,0);
		addingPoints[1]= new Point(0,BALL_RADIUS);
		addingPoints[2]= new Point(BALL_RADIUS,BALL_RADIUS);
		addingPoints[3]= new Point(BALL_RADIUS,0);
		for (int i =0; i< 4; i++)
		{
			coll = getElementAt(ballPoint.getX()+addingPoints[i].x, ballPoint.getY()+addingPoints[i].y);
			if (coll != null)
					return coll;
		}
		return null;
	}
 
	// display the text on the screen to give some information.
	private void displayText(String text)
	{
		textDisplay = new GLabel(text,(WIDTH/2 - (text.length() * TEXT_SIZE)), HEIGHT/2);
		textDisplay.setFont(Font.SANS_SERIF);
		add(textDisplay);
	}
 
	// display the status , score and also lives left
	private void displayStatus()
	{
		if (status != null) remove(status);
		status = new GLabel("Score : "+score +" : Lives : "+lifesLeft,WIDTH/2, (BRICK_HEIGHT*2));
		add(status);
	}
 
	// difference between two points.
	private GPoint differenceBetween(GPoint a, GPoint b)
	{
		return new GPoint(a.getX() - b.getX(), a.getY() - b.getY());
	}
 
	// type = true - extra life, false - extra score (+100) 
	private void generateExtra(Boolean type, GObject startingPoint)
	{
		if (type)
		{
			// create a extraLife object if not one already
			extraLife.setLocation(startingPoint.getX(), startingPoint.getY());
			extraLife.setColor(Color.MAGENTA);
			extraLife.setFilled(true);
			extraLife.setVisible(true);
			add(extraLife);
		}
		else
		{
			// create a extraScore object if not one already
			extraScore.setLocation(startingPoint.getX(), startingPoint.getY());
			extraScore.setColor(Color.GRAY);
			extraScore.setFilled(true);
			extraScore.setVisible(true);
			add(extraScore);
		}
	}
 
	/* move the extraLife/Score if they are visible.*/
	private void moveExtras()
	{
		if (extraLife.isVisible())
		{
			extraLife.move(0, EXTRAS_SPEED);
			if (extraLife.getLocation().getY() > HEIGHT) remove(extraLife);
		}
		if (extraScore.isVisible())
		{
			extraScore.move(0,EXTRAS_SPEED);
			if (extraScore.getLocation().getY() > HEIGHT) remove(extraScore);
		}
	}
/* Method: run() */
/** Runs the Breakout program. */
	public void run() {
		/* You fill this in, along with any subsidiary methods */
		getGCanvas().setSize(WIDTH, HEIGHT);
		GObject colliding;
		AudioClip bounceSound = MediaTools.loadAudioClip("woohoo.au");
		Boolean bottomHit, emptyBricks = true;
		int bricksLeft=0, paddleHit, randomExtra;
 
		extraLife = new GOval(0, 0, BALL_RADIUS,BALL_RADIUS);
		extraScore = new GOval(0,0, BALL_RADIUS, BALL_RADIUS);
		score = 0;
		lifesLeft = NTURNS;
		pauseDelay = PAUSE_DELAY;
		// loop through for the amount of lives
		while (lifesLeft > 0)
		{
			/* setup the display and then ask the user to click to start */
 
			if (emptyBricks)
			{
				/* clear the screen and rebuild the bricks, if come to the end of that level, or just starting*/
				removeAll();
				bricksLeft = NBRICKS_PER_ROW * NBRICK_ROWS;
				CreateBricks();
				addPaddle();
				addMouseListeners();
				emptyBricks = false;
			}
			displayText("Click to start");
			displayStatus();
			waitForClick();
			remove(textDisplay);
			setUpBall();
			paddleHit =0;
			bottomHit =false;
			randomExtra = rgen.nextInt(5);
			/* end of setup display*/
			while (true)
			{
				// moveBall returns true if the bottom has been hit.
				if (moveBall()) {
					bottomHit = true;
					break;
				}
				moveExtras();
				colliding = getCollidingObject(ballObject);
				// if the ball has collided with anything
				if (colliding != null)
				{
					if (!colliding.equals(paddleObject))
					{
						if (!colliding.equals(status))
						{
							if (randomExtra <=0)
							{
								randomExtra = rgen.nextInt(5);
								// create a extra life if next random extra is even else extra score
								if ((randomExtra % 2)==0)
									generateExtra(true,colliding);
								else
									generateExtra(false,colliding);
							}
							// delete the object that we just collided with, e.g. the brick
							if (colliding.getClass().getSimpleName().equalsIgnoreCase("GRect"))
							{
								// decrement the randomExtra 
								randomExtra--;
								// increment the score by finding out the colour of brick that we hit.
								Color collidColor = colliding.getColor();
								for (int i = (NBRICK_ROWS-1); i > 0; i--)
								{
									score++;
									if (colours[i] == collidColor) break;
								}
								displayStatus();
								remove(colliding);
								/* if no more bricks.. exit */
								bricksLeft--;
								if (bricksLeft == 0) 
								{ 
									emptyBricks = true; 
									break;
								}
								/*//another way of checking to see if there is any GRects left.!!.
								emptyBricks = true;
								for (int i =0; i < getElementCount(); i++)
								{
									if (getElement(i).getClass().getSimpleName().equalsIgnoreCase("GRect"))
									{
										if (!getElement(i).getClass().equals(paddleObject))
										{
											emptyBricks= false;
										ou	break;
										}	
									}
								}
								if (emptyBricks) break;
								 */
								ballVY = -ballVY;
							}
						}
					}
					else
					{
						// handle the increase of speed (less delay)
						paddleHit++;
						if ((paddleHit % 7) ==0) pauseDelay--;
 
						bounceSound.play();
						GPoint difBetween = differenceBetween(ballObject.getLocation(), colliding.getLocation());
						ballVX = (difBetween.getX() - (PADDLE_WIDTH/2))/2;
						ballVY = -(difBetween.getY()/BALL_SPEED);
						// for when the ball may try and bounce of the side of the bat and then the wall!!.
						if (ballVY == 0.0) ballVY = 1.0;
						// move in the opposite direction
						ballVY = -ballVY;
					}
				}
				/* extra items */
				colliding = getCollidingObject(extraLife);
				if (colliding != null)
				{
					if (colliding.equals(paddleObject))
					{
						extraLife.setVisible(false);
						lifesLeft++;
						displayStatus();
					}
				}
				colliding = getCollidingObject(extraScore);
				if (colliding != null)
				{
					if (colliding.equals(paddleObject))
					{
						extraScore.setVisible(false);
						score+=EXTRA_SCORE_VALUE;
						displayStatus();
					}
				}
				pause(pauseDelay);
			}
			if (bottomHit) 
			{
				lifesLeft--;
				displayStatus();
				if (lifesLeft > 0)
				{
					displayText("Bottom hit : lives left = " + lifesLeft);
				}
				else 
					displayText("End of Game : your score " + score);
			}
			if (emptyBricks)
				displayText("Yeppy you do it!! click to start again");
			waitForClick();
			remove(textDisplay);
		}
	}
 
	private GRect paddleObject;
	private GOval ballObject, extraScore, extraLife;
	private GLabel textDisplay, status;
	private double ballVX, ballVY, ballX, ballY;
	private int lifesLeft, score, pauseDelay;
	private RandomGenerator rgen = RandomGenerator.getInstance();
	private Color colours[] = new Color[NBRICK_ROWS];
}